﻿using System.Collections;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Text;

namespace Utility
{
    public class IniFile
    {
        public const int MaxSectionSize = 1310680; // 在一个INI文件一行的最大尺寸32*2*20 KB
        private string m_path; //ini文件路径
        private static Dictionary<string, object> _locks = new Dictionary<string, object>();

        #region 构造函数
        /// <summary>
        /// 初始化这个类 <see cref="IniFile"/> .
        /// </summary>
        /// <param name="path">ini文件的路径</param>
        public IniFile(string path)
        {
            //Convert to the full path.  Because of backward compatibility, 
            // the win32 functions tend to assume the path should be the 
            // root Windows directory if it is not specified.  By calling 
            // GetFullPath, we make sure we are always passing the full path
            // the win32 functions.
            m_path = System.IO.Path.GetFullPath(path);
            if (!_locks.ContainsKey(m_path))
            {
                _locks.Add(m_path, new object());
            }
        }
        #endregion

        #region 获取路径
        /// <summary>
        /// 得到这个对象实例操作ini文件的完整路径.
        /// </summary>
        /// <value>一个文件路径.</value>
        public string Path
        {
            get
            {
                return m_path;
            }
        }
        #endregion

        #region P/Invoke 声明
        /// <summary>
        /// 一个静态类提供了win32 P /Invoke这个类所使用的签名. 
        /// </summary>
        /// <remarks>
        /// 注意:在每个下面的声明,我们显式地设置字符集。
        /// 在c#中,默认字符集设置为Ansi,
        /// 这降低了性能在windows 2000及以上由于需要将字符串从Unicode(原生格式 净字符串)之前Ansi编组。
        /// 使用字符集选择Unicode版本的这些函数时可用。
        /// </remarks>
        [System.Security.SuppressUnmanagedCodeSecurity] //系统.安全.抑制非托管代码安全
        private static class NativeMethods //本地方法
        {
            [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
            //在一个初始化文件中检取所有段的名称，它是为兼容16位WINDOWS应用程序提供的
            //IntPtr类型称为“平台特定的整数类型”，它们用于本机资源，如窗口句柄
            public static extern int GetPrivateProfileSectionNames(IntPtr lpszReturnBuffer,
                                                                   uint nSize,
                                                                   string lpFileName);

            /// <summary>
            /// 从给定初始化文件的指定段中检取一字符串
            /// </summary>
            /// <param name="lpAppName">要读取的段落名</param>
            /// <param name="lpKeyName">要读取的键</param>
            /// <param name="lpDefault">读取异常的情况下的缺省值；如果Key值没有找到，则返回缺省的字符串的地址</param>
            /// <param name="lpReturnedString">key所对应的值，如果该key不存在则返回空值</param>
            /// <param name="nSize">返回值允许的大小</param>
            /// <param name="lpFileName">INI文件的完整路径和文件名</param>
            /// <returns></returns>
            [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
            public static extern uint GetPrivateProfileString(string lpAppName,
                                                              string lpKeyName,
                                                              string lpDefault,
                                                              StringBuilder lpReturnedString,
                                                              int nSize,
                                                              string lpFileName);

            [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
            public static extern uint GetPrivateProfileString(string lpAppName,
                                                              string lpKeyName,
                                                              string lpDefault,
                                                              [In, Out] char[] lpReturnedString,
                                                              int nSize,
                                                              string lpFileName);

            [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
            public static extern int GetPrivateProfileString(string lpAppName,
                                                             string lpKeyName,
                                                             string lpDefault,
                                                             IntPtr lpReturnedString,
                                                             uint nSize,
                                                             string lpFileName);

            /// <summary>
            /// 检取初始化文件中与指定段某个关键字相关的整数
            /// </summary>
            /// <param name="lpAppName">指向包含Section名称的字符串地址</param>
            /// <param name="lpKeyName">指向包含Key名称的字符串地址</param>
            /// <param name="nDefault">如果Key 值没有找到，则返回缺省的值是多少</param>
            /// <param name="lpFileName">INI文件的完整路径和文件名</param>
            /// <returns>返回获得的整数值</returns>
            [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
            public static extern int GetPrivateProfileInt(string lpAppName,
                                                          string lpKeyName,
                                                          int lpDefault,
                                                          string lpFileName);
            /// <summary>
            /// 从给定的初始化文件中检取指定段的所有关键字和值
            /// </summary>
            /// <param name="lpAppName">欲获取的小节。注意这个字串不区分大小写</param>
            /// <param name="lpReturnedString">缓冲区</param>
            /// <param name="nSize">lpReturnedString缓冲区的大小</param>
            /// <param name="lpFileName">初始化文件的名字。如没有指定完整路径名，windows就在Windows目录中查找文件</param>
            /// <returns>装载到lpReturnedString缓冲区的字符数量。如缓冲区的容量不够大，不能容下所有信息，就返回nSize-2</returns>
            [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
            public static extern int GetPrivateProfileSection(string lpAppName,
                                                              IntPtr lpReturnedString,
                                                              uint nSize,
                                                              string lpFileName);

            //我们在这里显式地启用SetLastError属性通过SetLastError因为WritePrivateProfileString返回错误。
            //未能设置可能导致错误迷失在marshal回托管代码。

            /// <summary>
            /// 写操作
            /// </summary>
            /// <param name="section">要写入的段落名</param>
            /// <param name="key">要写入的键，如果该key存在则覆盖写入</param>
            /// <param name="val">key所对应的值</param>
            /// <param name="filePath">INI文件的完整路径和文件名</param>
            /// <returns></returns>
            [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
            public static extern bool WritePrivateProfileString(string lpAppName,
                                                                string lpKeyName,
                                                                string lpString,
                                                                string lpFileName);
            /// <summary>
            /// 为一个初始化文件（.ini）中指定的小节设置所有项名和值
            /// </summary>
            /// <param name="lpAppName">要设置的小节。这个字串不区分大小写 </param>
            /// <param name="lpstring">项和值字串的一个列表。每个字串都用一个NULL字符分隔，最后一个字串后面用两个NULL表示中止。
            /// 如lpAppName指定的小节不存在，则用那个名字新建一个小节，并将其追加到初始化文件的最后。
            /// 如果存在，则当前的所有项名和值都会被这个缓冲区中指定的数据取代 </param>
            /// <param name="lpFileName">初始化文件的名字。如指定了一个完整路径，而且文件不存在，就会产生错误。
            /// 如只指定了文件名，而且文件不存在，就在当前的windows目录中创建它</param>
            /// <returns>非零表示成功，零表示失败。会设置GetLastError</returns>
            [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
            public static extern bool WritePrivateProfileSection(string lpAppName,
                                                                 string lpstring,
                                                                 string lpFileName);

        }
        #endregion

        #region GetValue Methods

        /// <summary>
        /// Gets the value of a setting in an ini file as a <see cref="T:System.String"/>.
        /// </summary>
        /// <param name="sectionName">头值</param>
        /// <param name="keyName">键值</param>
        /// <param name="defaultValue">缺省值</param>
        /// <returns>The value of the key, if found.  Otherwise, returns 
        /// <paramref name="defaultValue"/></returns>
        /// <remarks>
        /// 检索的值必须小于32 kb的长度.
        /// </remarks>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="sectionName"/> or <paramref name="keyName"/> are 
        /// a null reference  (Nothing in VB)
        /// </exception>
        public string GetString(string sectionName, string keyName, string defaultValue)
        {
            if (sectionName == null)
                throw new ArgumentNullException("sectionName");
            if (keyName == null)
                throw new ArgumentNullException("keyName");
            lock (_locks[m_path])
            {
                StringBuilder retval = new StringBuilder(IniFile.MaxSectionSize);

                NativeMethods.GetPrivateProfileString(sectionName,
                                                      keyName,
                                                      defaultValue,
                                                      retval,
                                                      IniFile.MaxSectionSize,
                                                      m_path);
                return retval.ToString();
            }
        }

        public int GetInt16(string sectionName, string keyName, short defaultValue)
        {
            int retval = GetInt32(sectionName, keyName, defaultValue);

            return Convert.ToInt16(retval);
        }

        public int GetInt32(string sectionName, string keyName, int defaultValue)
        {
            if (sectionName == null)
                throw new ArgumentNullException("sectionName");
            if (keyName == null)
                throw new ArgumentNullException("keyName");
            lock (_locks[m_path])
            {
                return NativeMethods.GetPrivateProfileInt(sectionName, keyName, defaultValue, m_path);
            }
        }

        public double GetDouble(string sectionName, string keyName, double defaultValue)
        {
            string retval = GetString(sectionName, keyName, "");

            if (retval == null || retval.Length == 0)
            {
                return defaultValue;
            }

            return Convert.ToDouble(retval, CultureInfo.InvariantCulture);
        }

        #endregion

        #region GetSectionValues Methods

        public List<KeyValuePair<string, string>> GetSectionValuesAsList(string sectionName)
        {
            List<KeyValuePair<string, string>> retval;
            string[] keyValuePairs;
            string key, value;
            int equalSignPos;
            if (sectionName == null) throw new ArgumentNullException("sectionName");
            IntPtr ptr = Marshal.AllocCoTaskMem(IniFile.MaxSectionSize); //从 COM 任务内存分配器分配指定大小的内存块。
            try
            {
                lock (_locks[m_path])
                {
                    int len = NativeMethods.GetPrivateProfileSection(sectionName, ptr, IniFile.MaxSectionSize, m_path);
                    keyValuePairs = ConvertNullSeperatedStringToStringArray(ptr, len); //空分离字符串转换为字符串数组
                }
            }
            finally
            {
                Marshal.FreeCoTaskMem(ptr); //释放
            }
            retval = new List<KeyValuePair<string, string>>(keyValuePairs.Length);
            for (int i = 0; i < keyValuePairs.Length; ++i)
            {
                equalSignPos = keyValuePairs[i].IndexOf('='); //确定=号的
                key = keyValuePairs[i].Substring(0, equalSignPos);
                value = keyValuePairs[i].Substring(equalSignPos + 1, keyValuePairs[i].Length - equalSignPos - 1);
                retval.Add(new KeyValuePair<string, string>(key, value));
            }
            return retval;
        }

        public Dictionary<string, string> GetSectionValues(string sectionName)
        {
            List<KeyValuePair<string, string>> keyValuePairs;
            Dictionary<string, string> retval;
            keyValuePairs = GetSectionValuesAsList(sectionName);
            retval = new Dictionary<string, string>(keyValuePairs.Count);
            foreach (KeyValuePair<string, string> keyValuePair in keyValuePairs)
            {
                if (!retval.ContainsKey(keyValuePair.Key)) //判断是否包含指定的键名 
                {
                    retval.Add(keyValuePair.Key, keyValuePair.Value);
                }
            }
            return retval;
        }

        public List<string> GetValues(string sectionName)
        {
            List<KeyValuePair<string, string>> keyValuePairs;
            Dictionary<string, string> retval;
            List<string> values = new List<string>();
            keyValuePairs = GetSectionValuesAsList(sectionName);
            retval = new Dictionary<string, string>(keyValuePairs.Count);
            foreach (KeyValuePair<string, string> keyValuePair in keyValuePairs)
            {
                //if (!retval.ContainsKey(keyValuePair.Key)) //判断是否包含指定的键名 
                {
                    values.Add(keyValuePair.Value);
                }
            }
            return values;
        }

        #endregion

        #region Get Key/Section Names

        public string[] GetKeyNames(string sectionName)
        {
            int len;
            string[] retval;
            if (sectionName == null) throw new ArgumentNullException("sectionName");
            IntPtr ptr = Marshal.AllocCoTaskMem(IniFile.MaxSectionSize);
            try
            {
                lock (_locks[m_path])
                {
                    len = NativeMethods.GetPrivateProfileString(sectionName, null, null, ptr, IniFile.MaxSectionSize, m_path);
                    retval = ConvertNullSeperatedStringToStringArray(ptr, len);
                }
            }
            finally
            {
                Marshal.FreeCoTaskMem(ptr);
            }
            return retval;
        }

        public string[] GetSectionNames()
        {
            string[] retval;
            int len;
            IntPtr ptr = Marshal.AllocCoTaskMem(IniFile.MaxSectionSize);
            try
            {
                lock (_locks[m_path])
                {
                    len = NativeMethods.GetPrivateProfileSectionNames(ptr, IniFile.MaxSectionSize, m_path);
                    retval = ConvertNullSeperatedStringToStringArray(ptr, len);
                }
            }
            finally
            {
                Marshal.FreeCoTaskMem(ptr);
            }
            return retval;
        }

        #endregion

        #region 辅助方法

        private static string[] ConvertNullSeperatedStringToStringArray(IntPtr ptr, int valLength)
        {
            string[] retval;
            if (valLength == 0)
            {
                retval = new string[0];
            }
            else
            {
                //缓冲区转换成一个字符串。所以长度减少1,我们删除第二个零结束。
                string buff = Marshal.PtrToStringAuto(ptr, valLength - 1);
                //缓冲区解析成一个字符串数组通过搜索null。
                retval = buff.Split('\0');
            }
            return retval;
        }

        #endregion

        #region Write Methods

        private void WriteValueInternal(string sectionName, string keyName, string value)
        {
            lock (_locks[m_path])
            {
                if (!NativeMethods.WritePrivateProfileString(sectionName, keyName, value, m_path))
                {
                    throw new System.ComponentModel.Win32Exception();
                }
            }

        }

        public void WriteValue(string sectionName)
        {
            WriteValueInternal(sectionName, "a", "a");
            WriteValueInternal(sectionName, "a", null);
        }

        public void WriteValue(string sectionName, string keyName, string value)
        {
            if (sectionName == null)
                throw new ArgumentNullException("sectionName");

            if (keyName == null)
                throw new ArgumentNullException("keyName");

            if (value == null)
                throw new ArgumentNullException("value");

            WriteValueInternal(sectionName, keyName, value);
        }

        public void WriteValue(string sectionName, string keyName, short value)
        {
            WriteValue(sectionName, keyName, (int)value);
        }

        public void WriteValue(string sectionName, string keyName, int value)
        {
            WriteValue(sectionName, keyName, value.ToString(CultureInfo.InvariantCulture));
        }

        public void WriteValue(string sectionName, string keyName, float value)
        {
            WriteValue(sectionName, keyName, value.ToString(CultureInfo.InvariantCulture));
        }

        public void WriteValue(string sectionName, string keyName, double value)
        {
            WriteValue(sectionName, keyName, value.ToString(CultureInfo.InvariantCulture));
        }

        #endregion

        #region Delete Methods

        public void DeleteKey(string sectionName, string keyName)
        {
            if (sectionName == null)
                throw new ArgumentNullException("sectionName");
            if (keyName == null)
                throw new ArgumentNullException("keyName");

            ArrayList list = ArrayList.Adapter(GetSectionNames());
            if (!list.Contains(sectionName)) //确定某元素是否在 ArrayList 中
            {
                return;
            }
            list = ArrayList.Adapter(GetKeyNames(sectionName));
            if (!list.Contains(keyName))
            {
                return;
            }
            WriteValueInternal(sectionName, keyName, null);
        }

        public void DeleteSection(string sectionName)
        {
            if (sectionName == null)
                throw new ArgumentNullException("sectionName");

            ArrayList list = ArrayList.Adapter(GetSectionNames());
            if (list.Contains(sectionName))
            {
                WriteValueInternal(sectionName, null, null);
            }
        }
        #endregion
    }
}
